using System;
using System.Collections.Generic;
using System.Linq;
using FubuMVC.Core;
using FubuMVC.Core.Urls;
using HtmlTags;

namespace complex.Controllers
{
    public class PagingController
    {
        private const int Adjacents = 2;
        private const int AdjacentsFactor = Adjacents*2;

        private readonly IUrlRegistry urls;

        private Pagination pagination;

        public PagingController(IUrlRegistry urls)
        {
            this.urls = urls;
        }

        [FubuPartial]
        public SparkHtmlTag Get(Pagination input)
        {
            pagination = input;

            var div = new HtmlTag("div").AddClass("pagination");
            return new SparkHtmlTag
                       {
                           Tag = pagination.RequiresPaging()
                                     ? CreateLinks().Aggregate(div, (root, tag) => root.Append(tag))
                                     : new NoTag()
                       };
        }

        private IEnumerable<HtmlTag> CreateLinks()
        {
            yield return PreviousTag();

            var elipsis = new HtmlTag("span").Text("...");

            if (ShouldNotBreakUpLinks())
            {
                foreach (var link in CreateNumberedLinks(1, pagination.TotalPages))
                {
                    yield return link;
                }
            }
            else
            {
                if (InTheMiddle())
                {
                    var lowerAdjacentIndex = pagination.CurrentPage - Adjacents;
                    var upperAdjacentIndex = pagination.CurrentPage + Adjacents;

                    foreach (var link in CreateNumberedLinks(1, 2))
                    {
                        yield return link;
                    }

                    // elipsis at begin is only required if there is more than one pagination between lower adjacent and ever displayed begin paginations
                    var requireElipsisBetweenBeginAndLowerAdjacentIndex = lowerAdjacentIndex - 2 > 1;
                    if (requireElipsisBetweenBeginAndLowerAdjacentIndex)
                    {
                        yield return elipsis;
                    }

                    foreach (var link in CreateNumberedLinks(lowerAdjacentIndex, upperAdjacentIndex))
                    {
                        yield return link;
                    }

                    // elipsis at end is only required if there is more than one pagination between upper adjacent and ever displayed end paginations
                    var requireElipsisBetweenUpperAdjacentIndexAndEnd = pagination.TotalPages - upperAdjacentIndex > 1;
                    if (requireElipsisBetweenUpperAdjacentIndexAndEnd)
                    {
                        yield return elipsis;
                    }

                    foreach (var link in CreateNumberedLinks(pagination.TotalPages - 1, pagination.TotalPages))
                    {
                        yield return link;
                    }
                }
                else if (AtTheBegining())
                {
                    foreach (var link in CreateNumberedLinks(1, 2 + AdjacentsFactor))
                    {
                        yield return link;
                    }

                    yield return elipsis;

                    foreach (var link in CreateNumberedLinks(pagination.TotalPages - 1, pagination.TotalPages))
                    {
                        yield return link;
                    }
                }
                else // at the end
                {
                    foreach (var link in CreateNumberedLinks(1, 2))
                    {
                        yield return link;
                    }

                    yield return elipsis;

                    foreach (var link in CreateNumberedLinks(pagination.TotalPages - (2 + AdjacentsFactor), pagination.TotalPages))
                    {
                        yield return link;
                    }
                }
            }

            yield return NextTag();
        }

        private bool ShouldNotBreakUpLinks()
        {
            const int breakupThreshold = 4 + AdjacentsFactor;
            return pagination.TotalPages < breakupThreshold;
        }

        private bool InTheMiddle()
        {
            var currentPageWellAfterTheBegining = pagination.CurrentPage > AdjacentsFactor;
            var currentPageWellBeforeThenEnd = pagination.TotalPages - AdjacentsFactor > pagination.CurrentPage;
            return currentPageWellAfterTheBegining && currentPageWellBeforeThenEnd;
        }

        private bool AtTheBegining()
        {
            return pagination.CurrentPage < pagination.TotalPages/2;
        }

        private HtmlTag PreviousTag()
        {
            const string caption = "<< prev";
            return CreateTag(pagination.PreviousPage(), caption, pagination.HasPreviousPage());
        }

        private IEnumerable<HtmlTag> CreateNumberedLinks(int start, int end)
        {
            for (var index = start; index <= end; index++)
            {
                var text = index.ToString();
                yield return index == pagination.CurrentPage
                                 ? new HtmlTag("span").AddClass("current").Text(text)
                                 : CreatePageLink(index, text);
            }
        }

        private HtmlTag NextTag()
        {
            const string caption = "next >>";
            return CreateTag(pagination.NextPage(), caption, pagination.HasNextPage());
        }

        private HtmlTag CreateTag(int page, string text, bool isLink)
        {
            return isLink
                       ? CreatePageLink(page, text)
                       : new HtmlTag("span").AddClass("disabled").Text(text);
        }

        private HtmlTag CreatePageLink(int page, string text)
        {
            var model = pagination.UrlModel(page);
            var url = urls.UrlFor(model);
            return new LinkTag(text, url);
        }
    }

    public class Pagination
    {
        public int CurrentPage { get; set; }
        public int TotalPages { get; set; }
        public Func<int, object> UrlModel { get; set; }

        public bool RequiresPaging()
        {
            return TotalPages > 1;
        }

        public bool HasPreviousPage()
        {
            return PreviousPage() > 0;
        }

        public int PreviousPage()
        {
            return CurrentPage - 1;
        }

        public bool HasNextPage()
        {
            return NextPage() <= TotalPages;
        }

        public int NextPage()
        {
            return CurrentPage + 1;
        }
    }
}